package d3s.net.papervision3d.objects
{
	import org.papervision3d.core.proto.MaterialObject3D;
	import flash.events.Event;
	import org.papervision3d.core.proto.DisplayObjectContainer3D;
	import org.papervision3d.core.geom.Mesh3D;
	import org.papervision3d.core.Matrix3D;
	import org.papervision3d.materials.MaterialsList;
	import flash.net.URLLoader;
	import flash.net.URLRequest;
	import org.papervision3d.objects.DisplayObject3D;
	import org.papervision3d.core.geom.Vertex3D;
	import org.papervision3d.core.geom.Face3D;
	import org.papervision3d.materials.ColorMaterial;
	import org.papervision3d.materials.WireframeMaterial;
	import org.papervision3d.Papervision3D;
	import org.papervision3d.core.NumberUV;
	import d3s.net.papervision3d.animation.Frame;
	import org.papervision3d.objects.WallMesh;

	public class MotionCollada extends DisplayObject3D
	{
		
		private var COLLADA:XML;
        private var material_list:MaterialsList;
        private var scaling:Number;
        private var yUp:Boolean;
        
        private var _p : DisplayObjectContainer3D;
        
        
        private namespace animated;
        private namespace rock;
    
        private var currentNS : Namespace;
    
		public function MotionCollada( COLLADA :XML ,smaterials:MaterialsList = null, scale:Number = 1, p : DisplayObjectContainer3D = null)
		{
			currentNS = rock;
			this._p = p;
            
            this.material_list = smaterials;
            this.materials = new MaterialsList();
    
            this.scaling = (scale || DEFAULT_SCALING) * INTERNAL_SCALING;
    
            
            this.COLLADA = COLLADA;
			this._filename = "";
			buildCollada();
			
			
            material_list = null;
		}
		
		
		// _______________________________________________________________________ PRIVATE

		private function loadCollada():void
		{
			this._loader = new URLLoader();
			this._loader.addEventListener( Event.COMPLETE, onComplete );
			this._loader.load( new URLRequest( this._filename ) );
		}
	
	
		private function onComplete(evt:Event):void
		{
			this.COLLADA = new XML( this._loader.data );
	
			buildCollada();
			
			add();
		}
        
        private function add() : void {
        	if( _p ) _p.addChild( this );
        }

        private function buildCollada():void
        {
        	//trace( COLLADA );
        	//var ns : Namespace = COLLADA.namespace()
            default xml namespace = COLLADA.namespace();
    
            // Get up axis
            this.yUp = (COLLADA.asset.up_axis == "Y_UP");
    
            // Parse first scene
            var sceneId:String = getId(COLLADA.scene.instance_visual_scene.@url);
    
            var scene:XML = COLLADA.library_visual_scenes.visual_scene.(@id == sceneId)[0];

    
            parseScene(scene);
            
            dispatchEvent( new Event( Event.COMPLETE ) );
        }

		// _______________________________________________________________________ PRIVATE

    
        private function parseScene(scene:XML):void
        {
            for each(var node:XML in scene.node)
                parseNode(node, this);
        }
    
        private function parseNode(node:XML, parent:DisplayObjectContainer3D):void
        {
            var matrix:Matrix3D = Matrix3D.IDENTITY;
    
            var newNode:DisplayObject3D;
    
            if (String(node.instance_geometry) == "")
            {
                newNode = new DisplayObject3D(node.@name);
                trace("DisplayObject3D");
            }
            else
            {
            	var animList : XMLList = COLLADA.library_animations.animation.( String(@id).lastIndexOf( node.@name, 0 ) !=-1 );
            	if( animList.length() == 0 ) {
                	newNode = new Mesh3D(null, node.@name);
            		if ( node.@name == "wall" ) newNode = new WallMesh(newNode as Mesh3D);
            		currentNS = rock;
            	} else {
                	newNode = new KeyframeMesh(null, node.@name, 30);
                	currentNS = animated;
	                trace("KeyframeMesh " + String(newNode is KeyframeMesh));
            	}
            }

            var instance:DisplayObject3D = parent.addChild(newNode, node.@name);
    
            var children:XMLList = node.children();
            var totalChildren:int = children.length();
    
            for (var i:int = 0; i < totalChildren; i++)
            {
                var child:XML = children[i];
    
                switch (child.name().localName)
                {
                    case "translate":
                        matrix = Matrix3D.multiply(matrix, translateMatrix(getArray(child)));
                        break;
    
                    case "rotate":
                        matrix = Matrix3D.multiply(matrix, rotateMatrix(getArray(child)));
                        break;
    
                    case "scale":
                        matrix = Matrix3D.multiply(matrix, scaleMatrix(getArray(child)));
                        break;
    
                    // Baked transform matrix
                    case "matrix":
                        matrix = Matrix3D.multiply(matrix, new Matrix3D(getArray(child)));
                        break;
    
                    case "node":
                        parseNode(child, instance);
                        break;

                    case "zoffset":
	                    try{
	                        instance.Zoffset = Number (child);
	                    } 
	                    catch( e : Error ) { 
	                    	trace( "tant pis" ) ;
	                    }
                        trace( instance, instance.Zoffset )
                        break;
    
                    case "instance_geometry":
                        for each(var geometry:XML in child)
                        {                       
                            if (String(geometry) == "")
                                continue;

                            var geoId:String = getId(geometry.@url);
                            var geo:XML = COLLADA.library_geometries.geometry.(@id == geoId)[0];
                            currentNS::parseGeometry(geo, instance as Mesh3D, Matrix3D.clone(matrix));
                        }
                        break;
                }
            }
    
            instance.copyTransform(matrix);
        }
        
         rock function parseGeometry(geometry:XML, instance:Mesh3D, matrix:Matrix3D):void
        {
            // Semantics
            var semantics :Object = new Object();
            semantics.name = geometry.@id;
    
            var faces:Array = semantics.triangles = new Array();
    
            // Multi material
            var multiMaterial:Boolean = (geometry.mesh.triangles.length() > 1);
    
            // Triangles
            for each(var triangles:XML in geometry.mesh.triangles)
            {
                // Input
                var field:Array = new Array();
    
                for each(var input:XML in triangles.input)
                {
                    semantics[ input.@semantic ] = deserialize(input, geometry);
                    field.push(input.@semantic);
                }
    
                var data     :Array  = triangles.p.split(' ');
                var len      :Number = triangles.@count;
                var material :String = triangles.@material;
    
                // DEBUG
                //trace("triangles: " + len);
                addMaterial(instance, material);
    
                for (var j:Number = 0; j < len; j++)
                {
                    var t:Object = new Object();
    
                    for (var v:Number = 0; v < 3; v++)
                    {
                        var fld:String;
                        for (var k:Number = 0; fld = field[k]; k++)
                        {
                            if (! t[ fld ]) t[ fld ] = new Array();
    
                            t[ fld ].push(Number(data.shift()));
                        }
    
                        t["material"] = material; //multiMaterial? material : null;
                    }
                    faces.push(t);
                }
            }
    
            currentNS:: buildObject(semantics, instance, matrix);
        }
    
        animated function parseGeometry(geometry:XML, instance:KeyframeMesh, matrix:Matrix3D):void
        {
        	trace( "parseGeometry");
        	//trace( geometry );
            // Semantics
            var semantics :Object = new Object();
            semantics.name = geometry.@id;
    
            var faces:Array = semantics.triangles = new Array();
    
            // Multi material
            var multiMaterial:Boolean = (geometry.mesh.triangles.length() > 1);
    
            // Triangles
            for each(var triangles:XML in geometry.mesh.triangles)
            {
                // Input
                var field:Array = new Array();
    
                for each(var input:XML in triangles.input)
                {
                    semantics[ input.@semantic ] = deserialize(input, geometry);
                    field.push(input.@semantic);
                }
    
                var data     :Array  = triangles.p.split(' ');
                var len      :Number = triangles.@count;
                var material :String = triangles.@material;
    
                // DEBUG
                //trace("triangles: " + len);
                addMaterial(instance, material);
    
                for (var j:Number = 0; j < len; j++)
                {
                    var t:Object = new Object();
    
                    for (var v:Number = 0; v < 3; v++)
                    {
                        var fld:String;
                        for (var k:Number = 0; fld = field[k]; k++)
                        {
                            if (! t[ fld ]) t[ fld ] = new Array();
    
                            t[ fld ].push(Number(data.shift()));
                        }
    
                        t["material"] = material; //multiMaterial? material : null;
                    }
                    faces.push(t);
                }
            }
            
            var animSem : Object = parseAnimation( geometry );
    		
            currentNS::buildObject(semantics, instance, matrix, animSem );
        }
        
        private function parseAnimation( geo : XML ) : Object {
        	
        	var geomId : String = geo.@id;
        	
        	var semantics :Object = new Object();
            semantics.name = geomId;
            
        	var animList : XMLList = COLLADA.library_animations.animation.( String(@id).lastIndexOf( geomId, 0 ) !=-1 );
        	//trace( animList );
        	
        	//trace( geomId + "-positions" );
        	var subIdLenght : int = (geomId + "-positions").length;
			//trace( subIdLenght );
        	var vertexId : int;
    		var regexp : RegExp = /.*\(([0-9]+)\).*/
        	
        	for each( var animData : XML in animList ) {
        		
        		
        		var strId : String =  String(animData.channel.@target);
        		vertexId = parseInt( strId.replace( regexp, "$1") );
        		
        		
        		var field:Array = new Array();
    
                for each(var input:XML in animData.sampler.input)
                {
                	if( semantics[ input.@semantic ] == null ) semantics[ input.@semantic ] = new Array();
                    semantics[ input.@semantic ][vertexId] = deserialize(input, animData);
                    field.push(input.@semantic);
                }
        		trace( semantics );
        		
        	}
        	
        	return semantics;
        }
    	
    	
    	rock function buildObject(semantics:Object, mesh:Mesh3D, matrix:Matrix3D):void
        {
            // Vertices
            var vertices:Array = mesh.vertices;
            var scaling:Number = this.scaling;
            var accVerts:Number= vertices.length;
    
            var semVertices:Array = semantics.VERTEX;
            var len:Number = semVertices.length;
    
            var i:int;
            for (i = 0; i < len; i++)
            {
                // Swap z & y for Max (to make Y up and Z depth)
                var vert:Object = semVertices[i];
                var x:Number = Number(vert.X) * scaling;
                var y:Number = Number(vert.Y) * scaling;
                var z:Number = Number(vert.Z) * scaling;
    
                if (this.yUp)
                    vertices.push(new Vertex3D(-x, y, z));
                else
                    vertices.push(new Vertex3D( x, z, y));
            }
    
            // Faces
            var faces:Array = mesh.faces;
            var semFaces:Array = semantics.triangles;
            len = semFaces.length;
    
            for (i = 0; i < len; i++)
            {
                // Triangle
                var tri:Array = semFaces[i].VERTEX;
                var a:Vertex3D = vertices[accVerts + tri[0]];
                var b:Vertex3D = vertices[accVerts + tri[1]];
                var c:Vertex3D = vertices[accVerts + tri[2]];
    
                var faceList :Array = [a, b, c];
    
                var tex:Array = semantics.TEXCOORD;
                var uv:Array = semFaces[i].TEXCOORD;
    
                var uvList:Array, uvA:NumberUV, uvB:NumberUV, uvC:NumberUV;
    
                if (uv && tex)
                {
                    uvA = new NumberUV(tex[uv[0]].S, tex[uv[0]].T);
                    uvB = new NumberUV(tex[uv[1]].S, tex[uv[1]].T);
                    uvC = new NumberUV(tex[uv[2]].S, tex[uv[2]].T);
    
                    uvList = [uvA, uvB, uvC];
                }
                else uvList = null;
    
                var materialName:String = semFaces[i].material || null;
    
                var face:Face3D = new Face3D(faceList, materialName, uvList);
                faces.push(face);
            }
    
            matrix.n14 *= scaling;
            matrix.n24 *= scaling;
            matrix.n34 *= scaling;
    
            mesh.material = new ColorMaterial(0xFF0000, 0.25);
    
            mesh.visible = true;
            
        }
        
        animated function buildObject(semantics:Object, mesh: KeyframeMesh, matrix:Matrix3D, animSem : Object):void
        {
            // Vertices
            var vertices:Array = mesh.vertices;
            var scaling:Number = this.scaling;
            var accVerts:Number= vertices.length;
    
            var semVertices:Array = semantics.VERTEX;
            var semVertexAnim : Array =animSem.OUTPUT;
            var len:Number = semVertices.length;
    		
    		var frameNumber : int = 0;
    		var frames : Array = mesh.frames;
    		
            var i:int;
            for (i = 0; i < len; i++)
            {
                // Swap z & y for Max (to make Y up and Z depth)
                var vert:Object = semVertices[i];
                var x:Number = Number(vert.X) * scaling;
                var y:Number = Number(vert.Y) * scaling;
                var z:Number = Number(vert.Z) * scaling;
    
                if (this.yUp)
                    vertices.push(new Vertex3D(-x, y, z));
                else
                    vertices.push(new Vertex3D( x, z, y));
                    
                if( semVertexAnim[i] ) frameNumber = Math.max(semVertexAnim[i].length, frameNumber );    
               
            }
            trace("MotionCollada.frameNumber : ", frameNumber );
            
            var frameVertices : Array ;
            var v : Vertex3D
            
            for( var fr : int = 0; fr < frameNumber; fr ++ ) {
            	frameVertices = new Array();
            	for each( v in vertices ) frameVertices.push( new Vertex3D( v.x, v.y, v.z ) );
            	frames.push( new Frame(fr.toString(), frameVertices) );
            }
            
            for (i = 0; i < len; i++)
            {
            	 // pour un vertex
                var frame : Frame;
                var f : Number ;
                var av : Vertex3D;
                
                if( semVertexAnim[i] ) { //_____________vertex animé
	                for( f = 0; f < frameNumber; f++) {
	                	frame = frames[f];
	                	av = frame.vertices[i];
	                	if( semVertexAnim[i][f].X ) av.x += semVertexAnim[i][f].X* scaling;
	                	if( semVertexAnim[i][f].Z ) av.y += semVertexAnim[i][f].Z* scaling;
	                	if( semVertexAnim[i][f].Y ) av.z += semVertexAnim[i][f].Y* scaling;
	                }
	            }
	       }
            
    		
            // Faces
            var faces:Array = mesh.faces;
            var semFaces:Array = semantics.triangles;
            len = semFaces.length;
    
            for (i = 0; i < len; i++)
            {
                // Triangle
                var tri:Array = semFaces[i].VERTEX;
                var a:Vertex3D = vertices[accVerts + tri[0]];
                var b:Vertex3D = vertices[accVerts + tri[1]];
                var c:Vertex3D = vertices[accVerts + tri[2]];
    
                var faceList :Array = [a, b, c];
    
                var tex:Array = semantics.TEXCOORD;
                var uv:Array = semFaces[i].TEXCOORD;
    
                var uvList:Array, uvA:NumberUV, uvB:NumberUV, uvC:NumberUV;
    
                if (uv && tex)
                {
                    uvA = new NumberUV(tex[uv[0]].S, tex[uv[0]].T);
                    uvB = new NumberUV(tex[uv[1]].S, tex[uv[1]].T);
                    uvC = new NumberUV(tex[uv[2]].S, tex[uv[2]].T);
    				
                    uvList = [uvA, uvB, uvC];
                }
                else uvList = null;
    
                var materialName:String = semFaces[i].material || null;
    
                var face:Face3D = new Face3D(faceList, materialName, uvList);
                faces.push(face);
                
            }
			
    
            matrix.n14 *= scaling;
            matrix.n24 *= scaling;
            matrix.n34 *= scaling;
    
            mesh.material = new ColorMaterial(0xFF0000, 0.25);
    
            mesh.visible = true;
            
          //  material_list = null;
        }
    
    
        private function getArray(spaced:String):Array
        {
            var strings:Array = spaced.split(" ");
            var numbers:Array = new Array();
    
            var totalStrings:Number = strings.length;
    
            for (var i:Number = 0; i < totalStrings; i++)
                numbers[i] = Number(strings[i]);
    
            return numbers;
        }
    
    
        private function addMaterial(mesh:Mesh3D, name:String):void
        {
             //trace("Collada: addMaterial: " + mesh.name + " > " + name); // DEBUG
            var material:MaterialObject3D = this.material_list.getMaterialByName(name);
    
            // Use default material if no material provided.
            if (!material)
            {
                material = new WireframeMaterial(Math.random() * 0xFFFFFF);
                material.name = name;
                material.fillAlpha = 1;
                material.fillColor = 0;
                material.oneSide = true;
    
                Papervision3D.log("Collada material " + name + " not found."); // TODO: WARNING
                
            }
            this.materials.addMaterial(material, name);
    
            if (!mesh.materials) 
                mesh.materials = this.materials;
    
        }
    
        private function rotateMatrix(vector:Array):Matrix3D
        {
            if (this.yUp)
                return Matrix3D.rotationMatrix(vector[0], vector[1], vector[2], -vector[3] *toRADIANS);
            else
                return Matrix3D.rotationMatrix(vector[0], vector[2], vector[1], -vector[3] *toRADIANS);
        }
    
    
        private function translateMatrix(vector:Array):Matrix3D
        {
            if (this.yUp)
                return Matrix3D.translationMatrix(-vector[0] * this.scaling, vector[1] * this.scaling, vector[2] * this.scaling);
            else
                return Matrix3D.translationMatrix( vector[0] * this.scaling, vector[2] * this.scaling, vector[1] * this.scaling);
        }
    
    
        private function scaleMatrix(vector:Array):Matrix3D
        {
            if (this.yUp)
                return Matrix3D.scaleMatrix(vector[0], vector[1], vector[2]);
            else
                return Matrix3D.scaleMatrix(vector[0], vector[2], vector[1]);
        }
    
        private function deserialize(input:XML, geo:XML):Array
        {
            var output:Array = new Array();
            var id:String = input.@source.split("#")[1];
    
            // Source?
            var acc:XMLList = geo..source.(@id == id).technique_common.accessor;
    
            if (acc != new XMLList())
            {
                // Build source floats array
                var floId:String  = acc.@source.split("#")[1];
                var floXML:XMLList = COLLADA..float_array.(@id == floId);
                var floStr:String  = floXML.toString();
                var floats:Array   = floStr.split(" ");
    
                // Build params array
                var params:Array = new Array();
    
                for each (var par:XML in acc.param)
                    params.push(par.@name);
    
                // Build output array
                var count:int = acc.@count;
                var stride:int = acc.@stride;
    
                for (var i:int = 0; i < count; i++)
                {
                    var element:Object = new Object();
    
                    for (var j:int = 0; j < stride; j++)
                        element[params[j]] = floats.shift();
    
                    output.push(element);
                }
            }
            else
            {
                // Store indexes if no source
                var recursive :XMLList = geo..vertices.(@id == id)[INPUTTAG];
    
                output = deserialize(recursive[0], geo);
            }
    
            return output;
        }
    
    
        private function getId(url:String):String
        {
            return url.split("#")[1];
        }

        // COLLADA tag names
    
        /*
        private static var COLLADASECTION  :String = "COLLADA";
        private static var LIBRARYSECTION  :String = "library";
        private static var ASSETSECTION    :String = "asset";
        private static var SCENESECTION    :String = "scene";
    
        private static var LIGHTPREFAB     :String = "light";
        private static var CAMERAPREFAB    :String = "camera";
        private static var MATERIALSECTION :String = "material";
        private static var GEOMETRYSECTION :String = "geometry";
    
        private static var MESHSECTION     :String = "mesh";
        private static var SOURCESECTION   :String = "source";
        private static var ARRAYSECTION    :String = "array";
        private static var ACCESSORSECTION :String = "accessor";
        private static var VERTICESSECTION :String = "vertices";*/
        private static var INPUTTAG        :String = "input";
        /*private static var POLYGONSSECTION :String = "polygons";
        private static var POLYGON         :String = "p";
        private static var NODESECTION     :String = "node";
        private static var LOOKATNODE      :String = "lookat";
        private static var MATRIXNODE      :String = "matrix";
        private static var PERSPECTIVENODE :String = "perspective";
        private static var ROTATENODE      :String = "rotate";
        private static var SCALENODE       :String = "scale";
        private static var TRANSLATENODE   :String = "translate";
        private static var SKEWNODE        :String = "skew";
        private static var INSTANCENODE    :String = "instance";
        private static var INSTACESCENE    :String = "instance_visual_scene";
    
        private static var PARAMTAG        :String = "param";
    
        private static var POSITIONINPUT   :String = "POSITION";
        private static var VERTEXINPUT     :String = "VERTEX";
        private static var NORMALINPUT     :String = "NORMAL";
        private static var TEXCOORDINPUT   :String = "TEXCOORD";
        private static var UVINPUT         :String = "UV";
        private static var TANGENTINPUT    :String = "TANGENT";
        */
        // Default scaling value for constructor.
        static public var DEFAULT_SCALING  :Number = 1;
    
        // Internal scaling value.
        static private var INTERNAL_SCALING :Number = 100;
    	
		private var _loader     :URLLoader;
		private var _filename     :String;
        // _______________________________________________________________________
    
        static private var toDEGREES :Number = 180 / Math.PI;
        static private var toRADIANS :Number = Math.PI / 180;
    }
}